package com.example.tobebigfe.web

import android.content.Context
import android.util.Log
import com.example.tobebigfe.utils.ZipUtils
import okhttp3.*
import java.io.File
import java.io.FileOutputStream
import java.io.IOException


typealias OfflineZipUpdatedCallback = (error: Throwable?) -> Unit

class ZipManager(val context: Context, val httpClient: OkHttpClient) {

    private val unzipOkFileName = "unzipOK.file"
    private val zipBasePath: File = File(context.cacheDir, "WebZip")
    private val unzipBasePath: File = context.getDir("web", Context.MODE_PRIVATE)

    private val zipUpdatingCallbackMap = mutableMapOf<String, MutableList<OfflineZipUpdatedCallback>>()

    fun hasOfflineFiles(id: String, version: String): Boolean {
        return getOfflineFile(id, version, unzipOkFileName).exists()
    }

    fun getOfflineFile(id: String, version: String, filePath: String): File {
        return File(unzipBasePath, "$id/$version/$filePath")
    }

    fun unzipBuiltin(id: String, version: String) {
        Log.e("ZipManager", "unzip builtin $id $version")
        val input = context.assets.open("$id.zip")
        val destPath = File(unzipBasePath, "$id/$version")
        ZipUtils.unzip(input, destPath)
        File(destPath, unzipOkFileName).createNewFile()
    }

    fun updateOfflineZip(id: String, version: String, callback: OfflineZipUpdatedCallback): Call? {
        if (hasOfflineFiles(id, version)) {
            callback(null)
            return null
        }

        if (zipUpdatingCallbackMap.containsKey(id)) {
            val callbackList = zipUpdatingCallbackMap[id]
            callbackList!!.add(callback)
            return null
        }

        Log.e("ZipManager", "update zip")

        val callbackList = mutableListOf<OfflineZipUpdatedCallback>()
        callbackList.add(callback)
        zipUpdatingCallbackMap[id] = callbackList

        if (!zipBasePath.exists()) {
            zipBasePath.mkdirs()
        }

        val zipUrl = "${WebConst.WEB_BASE_URL}$id/$version/$id.zip"
        val zipDownloadFile = File(zipBasePath, "$id-$version.zip")
        if (zipDownloadFile.exists()) {
            zipDownloadFile.delete()
        }

        Log.e("ZipManager", "download $zipUrl")

        val request = Request.Builder()
            .url(zipUrl)
            .build()

        val call = httpClient.newCall(request)
        call.enqueue(object: Callback {

            fun callbackAll(error: Throwable?) {
                Log.e("ZipManager", "update zip complete. ${error?.localizedMessage ?: ""}")
                callbackList.forEach {
                    it.invoke(error)
                }
                zipUpdatingCallbackMap.remove(id)
            }

            override fun onFailure(call: Call, e: IOException) {
                callbackAll(e)
            }

            override fun onResponse(call: Call, response: Response) {
                if (!response.isSuccessful) {
                    callbackAll(IOException("Request fail: ${response.code}."))
                    return
                }

                val bodyStream = response.body?.byteStream()
                if (bodyStream == null) {
                    callbackAll(IOException("Response body error."))
                    return
                }

                try {
                    bodyStream.use {
                        val outputStream = FileOutputStream(zipDownloadFile)
                        val buf = ByteArray(DEFAULT_BUFFER_SIZE)
                        while (true) {
                            val len = it.read(buf)
                            if (len == -1) {
                                break
                            }
                            outputStream.write(buf, 0, len)
                        }
                        val destPath = File(unzipBasePath, "$id/$version")
                        ZipUtils.unzip(zipDownloadFile, destPath)
                        File(destPath, unzipOkFileName).createNewFile()
                    }
                } catch (error: Throwable) {
                    callbackAll(error)
                    return
                }
                callbackAll(null)
            }
        })
        return call
    }
}